﻿using System;
using System.Diagnostics;
using System.Collections;
//using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
//using System.Collections;

namespace ESYSShell
{
    /// <summary>
    /// this class is for Esys automation, since Eys opens a possible bat execution,
    /// we could manipulate the esys by console. 
    /// currently we focus on PDX import and Tal execution.
    /// before use, please pay attention that beside the in paras.
    /// we also need to modify some of the config files according to real usage.
    /// they are:
    /// config files under the root folder of esys program.
    /// <para>
    /// IN: 
    /// str_esys_proj               tells which project we would like to handle.currently we have
    ///                             NBT
    ///                             ENTRYNAV
    ///                             NBTEVO
    /// 
    /// str_esys_prom_path          string tells esys program path. by this path,we will get the info: 
    ///                             config file path        for console tal execution
    ///                             environment path        this is the path in console we need to run under.
    ///                             bat file path           all the console commands are using this bat file.
    ///                             
    /// str_esys_data_path          this is esys data path. by this path,we will get the info:
    ///                             psdzdata path           imported file location.
    ///                             TAL path                possible tal file location
    ///                             FA path                 possible FA file location
    ///                             log path                esys log file path
    ///                             
    /// str_pdx_path                this is pdx container's path, by this path, we will get the info:
    ///                             PDX path                where to find the pdx container
    ///                             tal path                where to find the tal file
    ///                             
    /// str_fa_path                 this is the absolute path of the FA file.
    /// str_tal_file_path           this is the absolute path of TAL file.
    ///                             
    /// OUT:
    /// al_execution_results      report the result of any execution.
    /// </para>
    /// </summary>
    public class Shell
    {
        #region member definition
        private esys iEsys;
        private esys_data iEsys_data;
        private proj iProj;
       // private log_handle iLog;
        private console_bat ibat;
        //private report iReport;
        private pdx_src iSrc;
        private config iCfg;

        private const string import = "import pdx";
        private const string clr_psdz = "clear old pdx";
        private const string tal_exec = "tal execution";
        private const string correct = "0";
        private const string warning = "1";
        private const string error = "2";

        private class esys {
            public string esys_path;           //esys root folder path. absolute.
            public string bat_path;            //bat file absolute path
            public string config_path;         //config file (for the tal execution) absolute path
            public string exec_path;           //execution path for the console application
            public string exec_tal_path;        //absolute path of the temp TAL file.
            public string exec_fa_path;         //absolute path of the temp FA file.
            public string org_fa_path;
            public string esys_config_path;
            public Hashtable tbl_esys_config;

            public esys(string esys_root_path,string tmp_fa_file) { 
                this.esys_path = esys_root_path;
                this.bat_path = esys_root_path+ "\\ESG\\E-Sys\\E-Sys.bat";
                this.config_path = esys_root_path+ "\\ESG\\E-Sys\\config.txt";
                this.exec_path = esys_root_path+ "\\ESG\\E-Sys\\lib\\environment";
                FileInfo fa_file = new FileInfo(tmp_fa_file);
                this.exec_fa_path = this.exec_path + "\\ESYSFA" + fa_file.Name;
                this.org_fa_path = tmp_fa_file;
                this.exec_tal_path = "";
                this.esys_config_path = esys_root_path + "\\ESG\\E-Sys\\config\\Esys.properties";
                this.tbl_esys_config = new Hashtable();
                if (!this.read_esys_config(this.esys_config_path, this.tbl_esys_config)) {
                    
                }
            }
            private Boolean read_esys_config(string config_path, Hashtable tbl_config){
                try
                {
                    log_handle.log_info("i", "start to read config file: [" + config_path + "]");
                    using (StreamReader sr = new StreamReader(config_path))
                    {
                        string line;
                        while (true )
                        {
                            line = sr.ReadLine();
                            if (line != null && line != "")
                            {
                                if (!line.Contains('#'))
                                {
                                    string[] temp_config_info_list = line.Split('=');
                                    tbl_config.Add(temp_config_info_list[0].Replace(" ", ""), temp_config_info_list[1].Replace(" ", ""));
                                }
                                else
                                {
                                    log_handle.log_info("i", "skip line due to # occurs: \n" + line);
                                    continue;
                                }
                            }
                            else {
                                break;
                            }
                        }
                        sr.Close();
                    }
                }
                catch (Exception e) {
                    log_handle.log_info("e", "read config file error: [" + e.Message + "]");
                    return false;
                }
                return true;
                
            }
        }

        private class esys_data {
            public string data_dir;        //esys data folder absolute path
            public string tal_dir;         //TAL folder under the esys data folder absolute path
            public string fa_dir;          //FA folder under the esys data folder absolute path
            public string psdzdata_path;    //imported data path;

            public esys_data(string esys_data_path) { 
                this.data_dir = esys_data_path;
                this.tal_dir = this.data_dir + "\\TAL";
                this.fa_dir = this.data_dir + "\\FA";
                this.psdzdata_path = this.data_dir + "\\psdzdata";
            }
            public Boolean del_old_imported_data(){
                try{
                    Directory.Delete(this.psdzdata_path,true);
                    Directory.CreateDirectory(this.psdzdata_path);
                }catch(Exception e){
                    log_handle.log_info("e", "error while delete old imported data: [" + e.Message + "]");
                    return false;
                }
                return true;
            }
        }

        private class xml_esys { }

        private class console_bat {
            public string out_put;
            private Process bat_process = new Process();
            public console_bat(string file, string arg) {
                this.bat_process.StartInfo.FileName = file;
                this.bat_process.StartInfo.UseShellExecute = false;
                this.bat_process.StartInfo.CreateNoWindow = true ;
                this.bat_process.StartInfo.RedirectStandardInput = true;
                this.bat_process.StartInfo.RedirectStandardOutput = true;
                this.bat_process.StartInfo.RedirectStandardError = true;
                this.bat_process.StartInfo.Arguments = arg;
                this.bat_process.Start();
                this.out_put = this.read_to_end();
                this.bat_process.WaitForExit();
                this.bat_process.Close();
            }

            private Boolean  write_line(string cmd,Boolean exit){
                try
                {
                    if (exit)
                    {
                        this.bat_process.StandardInput.WriteLine(cmd);
                        this.bat_process.StandardInput.WriteLine("exit");
                    }
                    else
                    {
                        this.bat_process.StandardInput.WriteLine(cmd);
                    }
                }
                catch (Exception e) {
                    log_handle.log_info("e", e.Message);
                    return false;
                }
                return true;
            }
            private string read_to_end() {
                //string output = "";
                StringBuilder try_str_builder = new StringBuilder();
                try
                {
                    while (!this.bat_process.StandardOutput.EndOfStream) {
                        try_str_builder.Append(this.bat_process.StandardOutput.ReadLine() + "" + Environment.NewLine);
                    }
                    return try_str_builder.ToString();
                    //output = this.bat_process.StandardOutput.ReadToEnd();
                }
                catch (Exception e) {
                    log_handle.log_info("e", e.Message);
                    return "";
                }
                return try_str_builder.ToString();
            }
            public Boolean exec_cmd(string cmd,Boolean exit) {
                if (this.write_line(cmd, exit))
                {
                    this.out_put = this.read_to_end();
                    return true;
                }
                else {
                    return false;
                }
            }
            public Boolean exit_proc() {
                try
                {
                    this.bat_process.Close();

                }
                catch (Exception e) {
                    log_handle.log_info("e", e.Message);
                    return false;
                }
                return true;
            }
        }

        private class proj {
            public string proj_name_esys_pdx_import;
            public string proj_name_esys_tal_exec;
            public string vehicle_info;
            public string proj_name;

            //public string target;   //e.g. HU, RSE all in capital
            //public string version;  //e.g. A12464A
            //public string content;  //e.g. ALL, NO_SPEECH

            public proj(string proj_txt) {
                if (proj_txt.Contains("entry") || proj_txt.Contains("ENTRY")) {
                    //this.proj_name_esys_tal_exec = "F010_ENTRYNAV";
                    this.vehicle_info = "F010";
                    this.proj_name = "ENTRYNAV";
                }else{
                    if(proj_txt.Contains("EVO") || proj_txt.Contains("evo")){
                        this.proj_name_esys_pdx_import = "xxxxevo";
                        this.vehicle_info = "xxxxevo";
                        this.vehicle_info = "xxxxevo";
                        this.proj_name_esys_tal_exec = "xxxxevo";
                    }else{
                        this.proj_name_esys_pdx_import = "xxxxnbt";
                        this.vehicle_info = "xxxxnbt";
                        this.vehicle_info = "xxxxnbt";
                        this.proj_name_esys_tal_exec = "xxxxnbt";
                    }
                }
            }
        }

        private class pdx_src{
            private string pdx_full_name;
            private string tal_full_name;
            private string pdx_name;
            private string tal_name;
            
            private string root_path;
            private string pdx_folder;
            private string cur_proj;
            public string target;
            public string version;
            public string content;
            
            public string get_full_pdx() {
                return this.pdx_full_name;
            }
            public string get_full_tal() {
                return this.tal_full_name;
            }
            public void set_tal_full_path(string s) {
                this.tal_full_name = s;
            }
            public string get_pdx() {
                return this.pdx_name;
            }
            public string get_tal() {
                return this.tal_name;
            }

            public pdx_src(string str_pdx_path,string str_cur_proj) {
                this.root_path = str_pdx_path;
                this.cur_proj = str_cur_proj;
                if (this.find_pdx()) {
                    string[] tmp_tal_list = new string[4];
                    tmp_tal_list = this.find_tal(str_cur_proj);
                    //this.tal_full_name = tmp_tal_list[0];
                    //this.target = tmp_tal_list[1];
                    //this.version = tmp_tal_list[2];
                    //this.content = tmp_tal_list[3];
                }
            }

            private Boolean find_pdx()
            {
                this.pdx_full_name = this.find_pdx_by_proj(this.cur_proj);
                if (this.pdx_full_name != "")
                {
                    return true;
                }
                else {
                    return false;
                }
            }
            private string[] find_tal(string proj) {
                string[] tal_string = new string[4];
                if (proj.Contains("entry") || proj.Contains("ENTRY"))
                {
                    if (this.pdx_folder != "")
                    {
                        DirectoryInfo di = new DirectoryInfo(this.pdx_folder);
                        foreach (FileInfo f in di.GetFiles())
                        {
                            if (f.Name.Contains("ALL") && f.Name.Contains("NO_SPEECH") && f.Name.Contains("xml"))
                            {
                                //FileInfo f_info = new FileInfo(f);
                                string[] release_info = f.Name.Split('_');
                                tal_string[0] = f.FullName ;  // tal file name
                                tal_string[1] = release_info[1];    //HU
                                tal_string[2] = release_info[2]; //Version
                                tal_string[3] = release_info[3].Replace(".xml", ""); //all
                                this.tal_name = f.Name;
                                log_handle.log_info("i", "find a valid tal file that could flash all .");
                                return tal_string;
                            }
                            else
                            {
                                ;
                                //if (f.Contains("ALL") && f.Contains("NO_SPEECH"))
                                //{
                                //    string[] rel_info = f.Split('_');
                                //    tal_string[0] = f;
                                //    tal_string[1] = rel_info[2];
                                //    tal_string[2] = "ALL_NO_SPEECH";
                                //    log_handle.log_info("i", "find a valid tal file could flash all except speech.");
                                //    return tal_string;
                                //}
                                //else {

                                //}
                            }
                        }

                    }
                    else
                    {
                        log_handle.log_info("w", "pdx file not found under " + this.root_path);
                        return tal_string;
                    }
                }
                else {
                    log_handle.log_info("w", "only Entry project implemented currently.");
                    return tal_string;
                }
                return tal_string;
            }
            private string find_pdx_by_proj(string proj) { 
                string tmp_pdx = "";
                if(proj.Contains("entry") || proj.Contains("ENTRY")){
                    foreach (string sub_dir in Directory.GetDirectories(this.root_path)){
                        DirectoryInfo dir_info = new DirectoryInfo(sub_dir);
                            foreach(FileInfo f in dir_info.GetFiles()){
                                if (f.Name.Substring(f.Name.Length -3) == "pdx" || f.Name.Substring(f.Name.Length -3) == "PDX"){
                                    tmp_pdx = f.FullName;
                                    this.pdx_folder = dir_info.FullName;
                                    this.pdx_name = f.Name;
                                    return tmp_pdx;
                                }
                            }
                    }
                log_handle.log_info("e", "no pdx file is found under " + this.root_path);
                return tmp_pdx;
                }else{
                    log_handle.log_info("w","only Entry project implemented currently.");
                    return tmp_pdx;
                }
            }
        }

        private class config {
            private Hashtable tbl_config = new Hashtable();
            private string config_path = "Config_EsysShell.txt";
            public string config_full_path = Directory.GetCurrentDirectory() + "\\" + "Config_EsysShell.txt";

            public Hashtable  get_tbl_config(){
                return this.tbl_config;
            }
            public config() {
                if (!load_config_file(this.config_path, this.tbl_config)) {
                    File.Create(this.config_full_path).Close();
                    //Environment.Exit(1);
                }
            }
            public void change_item(string key, string value) {
                if (this.tbl_config.ContainsKey(key))
                {
                    this.tbl_config[key] = value;
                }
                else {
                    this.tbl_config.Add(key, value);
                }

            }
            public string get_value(string key) {
                return this.tbl_config[key].ToString();
            }
            public static Boolean load_config_file(string config_path, Hashtable tbl_config){
                try
                {
                    log_handle.log_info("i", "start to read config file: [" + config_path + "]");
                    using (StreamReader sr = new StreamReader(config_path))
                    {
                        string line;
                        while (true )
                        {
                            line = sr.ReadLine();
                            if (line != null && line != "")
                            {
                                if (!line.Contains('#'))
                                {
                                    string[] temp_config_info_list = line.Split('=');
                                    tbl_config.Add(temp_config_info_list[0].Replace(" ", ""), temp_config_info_list[1].Replace(" ", ""));
                                }
                                else
                                {
                                    log_handle.log_info("i", "skip line due to # occurs: \n" + line);
                                    continue;
                                }
                            }
                            else {
                                break;
                            }
                        }
                        sr.Close();
                    }
                }
                catch (Exception e) {
                    log_handle.log_info("e", "read config file error: [" + e.Message + "]");
                    return false;
                }
                return true;
                
            }
            public Boolean write_new_cfg()
            {
                if(File.Exists(this.config_path)){
                    try
                    {
                        File.Delete(this.config_path);
                        File.Create(this.config_path).Close();
                    }
                    catch (Exception e) {
                        log_handle.log_info("e", "Error while create new config file: [" + e.Message + "]");
                        return false;
                    }
                }
                try
                {
                    using (StreamWriter sw = new StreamWriter(this.config_path)) { 
                        foreach( DictionaryEntry de in this.tbl_config){
                            sw.WriteLine(de.Key.ToString() + " = " + de.Value.ToString());
                        }
                        sw.Close();
                    }
                }
                catch (Exception e) {
                    log_handle.log_info("e", "error while write new config content: ["+ e.Message + "]");
                    return false;
                }
                return true;
            }
        }

        //private static class io_service {
        //    public static Boolean copy_file(string file_src_path, string file_tgt_path, Boolean overwrite_flag)
        //    {
        //        try
        //        {
        //            File.Copy(file_src_path, file_tgt_path, overwrite_flag);
        //        }
        //        catch (Exception e) {
        //            return false;
        //        }
        //        return true;
        //    }
        //    //public static Boolean copy_folder()
        //    public static Boolean del_file(string file_path) {
        //        try {
        //            File.Delete(file_path);
        //        }catch(Exception e){
        //            return false;
        //        }
        //        return true;
        //    }
        //}

        private static class log_handle { 
            private static string log_path = "esys_shell_log.txt";
            private static string esys_log_path = "esys_log.txt";
            private static int retry_count = 3;

            public static Boolean record_esys_trace(string esys_trace){
                try {
                    if (File.Exists(esys_log_path))
                    {
                        File.AppendAllText(esys_log_path, esys_trace);
                    }
                    else
                    {
                        File.Create(esys_log_path).Close();
                        File.AppendAllText(esys_log_path,esys_trace);
                    }
                }catch (Exception e){
                    log_handle.log_info("e", "error while record esys trace: [" + e.Message + "]");
                    return false;
                }
                return true;
                }
            public static void log_info(string type, string err_info)
            {
                if (!append_log(type,err_info)) {
                    while (retry_count > 0) {
                        if (!append_log(type,err_info))
                        {
                            retry_count -= 1;
                        }
                        else
                        {
                            retry_count = 0;
                        }
                    }
                }
            }
            private static Boolean append_log(string type, string err_info) {
                string pre_txt;
                switch (type) { 
                    case "w":
                        pre_txt = "##[WARNING]: ";
                        break;
                    case "i":
                        pre_txt = "[INFO]: ";
                        break;
                    case "e":
                        pre_txt = "##[ERROR]: ";
                        break;
                    default:
                        pre_txt = "[INFO]: ";
                        break;
                }
                string now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
                try {
                    File.AppendAllText(log_path, now + ": " + "\t" + pre_txt + err_info + "\n");
                }catch (Exception e){
                    return false;
                }
                return true;
            }
        }

        private static class expt_shell  
        {
            //public string err_process; //record the problematic process.
            //public string err_info;    //describe detailed error information
            //public static Exception excpt_shell;

            //public void log_exception(Exception e) {
            //    excpt_shell = e;
            //}

            public static void throw_expt() {
                throw new ApplicationException();
            }
        }

        #endregion

        #region constructor
        public Shell(string str_esys_proj, string str_esys_prom_path, string str_esys_data_path, string str_pdx_path,string str_fa_path,string str_tal_file_path)
        {
            this.iSrc = new pdx_src(str_pdx_path, str_esys_proj);
            this.iEsys = new esys(str_esys_prom_path,str_fa_path);
            this.iEsys_data = new esys_data(str_esys_data_path);
            this.iCfg = new config();
            this.iProj = new proj(str_esys_proj);
            this.iSrc.set_tal_full_path(str_tal_file_path);
            //this.iLog = new log_handle();
            //this.ibat = new console_bat("tmp");
            //this.iReport = new report();
        }
        #endregion

        /// <summary>
        /// one click flash will do the whole flash stuff altogether.
        /// -clear old imported pdx
        /// -import new pdx
        /// -tal execution
        /// </summary>
        /// <returns></returns>
        public int one_click_flash(int step = 0)
        {
            int report_int = 5 - step;
            while (step < 5) {
                switch (step)
                {
                    case 0:
                        if (this.del_old_pdx())
                        {
                            step += 1;
                            report_int -= 1;
                        }
                        else
                        {
                            return report_int;
                        }
                        break;
                    case 1:
                        if (this.locate_files())
                        {
                            step += 1;
                            report_int -= 1;
                        }
                        else
                        {
                            return report_int;
                        }
                        break;
                    case 2:
                        if (this.prepare_files())
                        {
                            step += 1;
                            report_int -= 1;
                        }
                        else
                        {
                            return report_int;
                        }
                        break;
                    case 3:
                        if (this.import_pdx())
                        {
                            step += 1;
                            report_int -= 1;
                        }
                        else
                        {
                            return report_int;
                        }
                        break;
                    case 4:
                        if (this.tal_execution())
                        {
                            step += 1;
                            report_int -= 1;
                        }
                        else
                        {
                            return report_int;
                        }
                        break;
                    default:
                        break;
                }
            }
            return report_int;
        }

        #region private interfaces definition
        private Boolean del_old_pdx()
        {
            //return true;              
            try
            {

                foreach (string x in Directory.GetDirectories(this.iEsys_data.psdzdata_path))
                {
                    Directory.Delete(x, true);
                }
            }
            catch (Exception e)
            {
                log_handle.log_info("e", "Error while delete old pdx: [" + e.Message + "]");
                return false;
            }
            return true;
        }
        //function name  locate_files
        //function will find the tal file, FA file and pdx file from the already initialized instances.
        //and assign those values to iCfg.
        //finally write a new cfg file. for the esys bat file use.
        private Boolean locate_files() {
            //return false;
            this.iCfg.change_item("PDX",this.iSrc.get_full_pdx());
            this.iCfg.change_item("TAL", this.iSrc.get_full_tal());
            this.iCfg.change_item("VEHICLEINFO", this.iProj.vehicle_info);
            //this.iCfg.change_item("GATEWAY_VIN",this.iEsys.tbl_esys_config["CurrentVehicleId"].ToString());
            string pdx_name = this.iSrc.get_pdx();
            this.iProj.proj_name_esys_pdx_import = pdx_name.Substring(0,pdx_name.Length - 4).Replace(".","_");
            this.iProj.proj_name_esys_tal_exec = this.iProj.vehicle_info + "_" + this.iProj.proj_name_esys_pdx_import;
            this.iCfg.change_item("PROJECT", this.iProj.proj_name_esys_tal_exec);
            if (this.iCfg.write_new_cfg())
            {
                return true;
            }
            else {
                log_handle.log_info("e", "error generate config file.");
                Environment.Exit(1);
                return false;
            }


        }
        //function name prepare_files
        //function will copy the FA file , tal file to esys exection path and rename the file to esys format.
        private Boolean prepare_files() {
            //return true;
            try
            {   //copy the tal file.
                string p = this.iEsys.exec_path + "\\" + "ESYSHU"+this.iSrc.get_tal();
                //File.Copy(this.iSrc.get_full_tal(),p);
                //copy the FA file.
                //File.Copy(this.iEsys.org_fa_path, this.iEsys.exec_fa_path);
            }
            catch (Exception e) {
                log_handle.log_info("e", "Error while copy temp TAL and FA file: [" + e.Message + "]");
                return false;
            }
            return true;
        }
        //function name: import_pdx
        //fucntion will do:
        //1. open the cmd environment
        //2. change to esys bat file path
        //3. 1st check the esys version and log the info
        //4. run the pdx import
        private Boolean import_pdx()
        {
            //return true;
            //string write_cmd;
            string read_info;
            log_handle.log_info("i", "now we start to mess with esys bat.");
            try
            {
                string arg = " -pdximport " + this.iSrc.get_full_pdx()+ " -project " + this.iProj.proj_name_esys_pdx_import;
                log_handle.log_info("i", "execute pdx import.");
                log_handle.log_info("i", "the arg is : [" + arg + "]");
                this.ibat = new console_bat(this.iEsys.bat_path, arg);
                read_info = this.ibat.out_put;
                log_handle.record_esys_trace(read_info);
                this.ibat.exit_proc();
            }
            catch (Exception e) {
                log_handle.log_info("e", "Error while import PDX: [" + e.Message + "]");
                return false;
            }
            return true;
        }
        private Boolean tal_execution()
        {
            //return true;
            string read_info;
            try {
                //string arg = " -talexecution " + this.iEsys.config_path;
                string arg = " -tal " + this.iSrc.get_full_tal() + " -fa " + this.iEsys.org_fa_path + " -project " + this.iProj.proj_name_esys_tal_exec + " -vehicleinfo " + this.iProj.vehicle_info;
                log_handle.log_info("i", "now we start to do tal execution.");
                log_handle.log_info("i", "the arg is: [" + arg + "]");
                log_handle.log_info("i", "the tal file is: [" + this.iSrc.get_full_tal() + "]");
                log_handle.log_info("i", "the fa file is: [" + this.iEsys.org_fa_path + "]");
                log_handle.log_info("i", "the pdx file is: [" + this.iSrc.get_full_pdx()+ "]");
                log_handle.log_info("i", "the project name is: [" + this.iProj.proj_name_esys_tal_exec + "]");
                log_handle.log_info("i", "the vehicle info is: [" + this.iProj.vehicle_info + "]");
                //log_handle.log_info("i", "the exec_fa file is: [" + this.iSrc.
                this.ibat = new console_bat(this.iEsys.bat_path, arg);
                read_info = this.ibat.out_put;
                log_handle.record_esys_trace(read_info);
                this.ibat.exit_proc();
            }
            catch (Exception e)
            {
                log_handle.log_info("e", "Error while execute TAL: [" + e.Message + "]");
                return false;
            }
            return true;
        }
        #endregion
    }
}
